﻿#region License

// Copyright (c) .NET Foundation and contributors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// The latest version of this file can be found at https://github.com/FluentValidation/FluentValidation

#endregion

namespace FluentValidation.Validators;

using System;
using System.Reflection;

public class EnumValidator<T, TProperty> : PropertyValidator<T, TProperty>, IEnumValidator {
	private readonly Type _enumType = typeof(TProperty);

	public Type EnumType => _enumType;

	public override string Name => "EnumValidator";

	public override bool IsValid(ValidationContext<T> context, TProperty value) {
		if (value == null) return true;

		var underlyingEnumType = Nullable.GetUnderlyingType(_enumType) ?? _enumType;

		if (!underlyingEnumType.IsEnum) return false;

		if (underlyingEnumType.GetCustomAttribute<FlagsAttribute>() != null) {
			return IsFlagsEnumDefined(underlyingEnumType, value);
		}

		return Enum.IsDefined(underlyingEnumType, value);
	}

	private static bool IsFlagsEnumDefined(Type enumType, object value) {
		var typeName = Enum.GetUnderlyingType(enumType).Name;

		switch (typeName) {
			case "Byte": {
				var typedValue = (byte)value;
				return EvaluateFlagEnumValues(typedValue, enumType);
			}

			case "Int16": {
				var typedValue = (short)value;

				return EvaluateFlagEnumValues(typedValue, enumType);
			}

			case "Int32": {
				var typedValue = (int)value;

				return EvaluateFlagEnumValues(typedValue, enumType);
			}

			case "Int64": {
				var typedValue = (long)value;

				return EvaluateFlagEnumValues(typedValue, enumType);
			}

			case "SByte": {
				var typedValue = (sbyte)value;

				return EvaluateFlagEnumValues(Convert.ToInt64(typedValue), enumType);
			}

			case "UInt16": {
				var typedValue = (ushort)value;
				return EvaluateFlagEnumValues(typedValue, enumType);
			}

			case "UInt32": {
				var typedValue = (uint)value;
				return EvaluateFlagEnumValues(typedValue, enumType);
			}

			case "UInt64": {
				var typedValue = (ulong)value;
				return EvaluateFlagEnumValues((long)typedValue, enumType);
			}

			default:
				var message = $"Unexpected typeName of '{typeName}' during flags enum evaluation.";
				throw new ArgumentOutOfRangeException(nameof(enumType), message);
		}
	}

	private static bool EvaluateFlagEnumValues(long value, Type enumType) {
		long mask = 0;
		foreach (var enumValue in Enum.GetValues(enumType)) {
			var enumValueAsInt64 = Convert.ToInt64(enumValue);
			if ((enumValueAsInt64 & value) == enumValueAsInt64) {
				mask |= enumValueAsInt64;
				if (mask == value)
					return true;
			}
		}

		return false;
	}

	protected override string GetDefaultMessageTemplate(string errorCode) {
		return Localized(errorCode, Name);
	}
}


public interface IEnumValidator : IPropertyValidator {

	/// <summary>
	/// Enum type being validated.
	/// </summary>
	Type EnumType { get; }
}
